/************************************
*  Copyright (C) 2010 Julien MOTTIN.
*  Contact: julien.mottin@gueydan.eu
*
*  This file is part of Gueydan.
*
*  Gueydan is free software: you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 3 of the License, or
*  (at your option) any later version.
*
*  Gueydan is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with Gueydan.  If not, see <http://www.gnu.org/licenses/>.
*
*************************************/
#ifndef RECIPE_H
#define RECIPE_H

// QtCore
#include <QString>
#include <QList>

// Local
#include "beerxmlobject.h"
#include "volume.h"
#include "duration.h"
#include "beerxmlhandler.h"
#include "style.h"
#include "color.h"

class Hop;
class Style;
class Fermentable;
class Yeast;

enum RecipeType
{
    RTExtract = 0,
    RTPartialMash,
    RTAllGrain
};

class Recipe : public BeerXMLObject
{
    Q_OBJECT;

public:

    // Standard Ctor without style field
    Recipe(const QString &iName,
           const int iVersion,
           const RecipeType &iType,
           const Style *ipStyle,
           const QString &iBrewer,
           const Volume &iBatchSize,
           const Volume &iBoilSize,
           const Duration &iBoilTime,
           const float iEfficiency=75.0f);

    // Standard operators
    Recipe();
    ~Recipe();
    bool operator==(const Recipe &iOther) const;
    bool operator!=(const Recipe &iOther) const;
    bool operator==(const BeerXMLObject &iOther) const;
    bool operator!=(const BeerXMLObject &iOther) const;

    /*
    * XML IO
    * returns :
    *   - 0 : Success
    *   - 1 : Invalid XML
    */
    int CreateFromXMLNode(const QDomNode &iNode);
    int ToXMLNode(QDomNode &iParentNode) const;
    
    /*
    * Check modified flag : save needed?
    */
    inline bool IsModified() const;

    /*
    * Ingredients definition
    */

    // Hop
    void AddHop(Hop *ipHop);
    int NbHops() const;
    const Hop * GetHop(const int iHopIdx) const;
    
    // Fermentable
    void AddFerm(Fermentable *ipFerm);
    int NbFerms() const;
    const Fermentable * GetFerm(const int iFermIdx) const;

    // Yeast
    void AddYeast(Yeast *ipYeast);
    int NbYeasts() const;
    const Yeast * GetYeast(const int iYeastIdx) const;
    
    /*
    * Indicators computation
    */

    /*
    * International Bitterness Unit (IBU)
    * 1 IBU = 1 milligram of isomerized alpha acid in 1 liter of wort/finished beer.
    * So basically it's a massic concentration
    * It can be computed thanks to the formula :
    *
    *    IBU = AAU * U / V
    *
    * where :
    *   AAU : Alpha Acid Units expressed in mg
    *   U   : Utilization (percentage). Correcting factor that gives the actual amount of
    *         Alpha Acid extracted during the Boil phasis
    *   V   : Volume of the Batch
    *
    * Utization is an approximated function, and can be calculated with 3 different ways :
    * the Rager, Garetz, and Tinseth estimation methods. Since Garetz is an iterative method,
    * it won't be implemented here
    *
    * ref : http://www.realbeer.com/hops/FAQ.html
    */
    enum HopUtilizationMethod
    {
        Rager = 0,
        Tinseth
    };

    // Compute utilization
    double HopUtilization(HopUtilizationMethod iMethod = Tinseth) const;

    // Compute AAU = mg of acid alpha in the wort
    double AAU() const;

    // Compute IBU
    double IBU(HopUtilizationMethod iMethod = Tinseth) const;


    /*
    * Gravity
    * The original gravity can be computed from the yield of the fermentables
    * Sugar has yield 100, other fermentabels have lower yields
    *
    * Extract potential of the sugar in points (=density degrees)/kg/L is 386.5
    *
    * ref : http://www.brewingtechniques.com/library/backissues/issue1.3/manning.html
    *
    * The final gravity depends on the efficiency of the yeast & the mash profile.
    */

    // Gravity estimation
    double OriginalGravity() const;
    double FinalGravity() const;

    // Compute alcohol by volume estimation.
    // Take de sugar for bottling into account
    double ABV() const;

    /*
    * Color estimation
    * The color can be computed thanks to the MCU (Malt Color Units)
    *
    * The Morey estimation will be used for this estimation (see resources)
    */

    Color ColorEstimate() const;

public:

    /*
    * Setters & const Getters
    */

    inline void SetType(const RecipeType &iType);
    inline const RecipeType & GetType() const;

    inline void SetStyle(const Style *ipStyle);
    inline const Style * GetStyle() const;

    inline void SetBrewerName(const QString &iBrewerName);
    inline const QString & GetBrewerName() const;

    inline void SetBatchSize(const Volume &iBatchSize);
    inline const Volume & GetBatchSize() const;

    inline void SetBoilSize(const Volume &iBoilSize);
    inline const Volume & GetBoilSize() const;

    inline void SetBoilTime(const Duration &iBoilTime);
    inline const Duration & GetBoilTime() const;

    inline void SetEfficiency(const float &iEfficiency);
    inline const float & GetEfficiency() const;

    inline void SetNotes(const QString &iNotes);
    inline const QString & GetNotes() const;

    inline void SetTasteNotes(const QString &iTasteNotes);
    inline const QString & GetTasteNotes() const;

    inline void SetOG(const double &iOG);
    inline const double & GetOG() const;

    inline void SetFG(const double &iFG);
    inline const double & GetFG() const;

signals:

    // Signal emitted when current recipe instance is modified
    void Modified(Recipe *iCurrentRecipe);

private:

    // forbidden
    Recipe(const Recipe &);
    Recipe & operator=(const Recipe &);

    // internal parse service
    bool ParseBeerXML(const QDomNode &iFather, const QString &iTag, QList<BeerXMLHandler>* pList);
    void Modify();
    void Recalc() const;

    // internal data
    RecipeType      _Type;
    BeerXMLHandler  _hStyle;
    QString         _BrewerName;
    Volume          _BatchSize;
    Volume          _BoilSize;
    Duration        _BoilTime;
    float           _Efficiency;
    QString         _Notes;
    QString         _TasteNotes;
    double          _OG,_FG;
    int             _NbStages;

    // Ingredient data
    QList<BeerXMLHandler> _Hops;
    QList<BeerXMLHandler> _Ferms;
    QList<BeerXMLHandler> _Yeasts;
    BeerXMLHandler _hMash;

    // Local estimations
    mutable double _OGEstimate,_FGEstimate;
    mutable double _ABV;
    mutable double _AAU;
    mutable Color  _ColorEstimate;

    // Status
    bool _Modified;
    mutable bool _NeedRecalc;

};

void Recipe::SetType(const RecipeType &iType)
{
    _Type = iType;
    Modify();
}

const RecipeType & Recipe::GetType() const
{
    return _Type;
}

void Recipe::SetStyle(const Style *ipStyle)
{
    _hStyle = ipStyle;
    Modify();
}

const Style * Recipe::GetStyle() const
{
    return (Style*)_hStyle;
}

void Recipe::SetBrewerName(const QString &iBrewerName)
{
    _BrewerName = iBrewerName;
    Modify();
}

const QString & Recipe::GetBrewerName() const
{
    return _BrewerName;
}

void Recipe::SetBatchSize(const Volume &iBatchSize)
{
    _BatchSize = iBatchSize;
    Modify();
}

const Volume & Recipe::GetBatchSize() const
{
    return _BatchSize;
}

void Recipe::SetBoilSize(const Volume &iBoilSize)
{
    _BoilSize = iBoilSize;
    Modify();
}

const Volume & Recipe::GetBoilSize() const
{
    return _BoilSize;
}

void Recipe::SetBoilTime(const Duration &iBoilTime)
{
    _BoilTime = iBoilTime;
    Modify();
}

const Duration & Recipe::GetBoilTime() const
{
    return _BoilTime;
}

void Recipe::SetEfficiency(const float &iEfficiency)
{
    _Efficiency = iEfficiency;
    Modify();
}

const float & Recipe::GetEfficiency() const
{
    return _Efficiency;
}

void Recipe::SetNotes(const QString &iNotes)
{
    _Notes = iNotes;
    Modify();
}

const QString & Recipe::GetNotes() const
{
    return _Notes;
}

void Recipe::SetTasteNotes(const QString &iTasteNotes)
{
    _TasteNotes = iTasteNotes;
    Modify();
}

const QString & Recipe::GetTasteNotes() const
{
    return _TasteNotes;
}

void Recipe::SetOG(const double &iOG)
{
    _OG = iOG;
    Modify();
}

const double & Recipe::GetOG() const
{
    return _OG;
}

void Recipe::SetFG(const double &iFG)
{
    _FG = iFG;
    Modify();
}

const double & Recipe::GetFG() const
{
    return _FG;
}

bool Recipe::IsModified() const {return _Modified;}

#endif // RECIPE_H
